package scheme;
import static accessstruct.Tool.loadPropFromFile;
import accessstruct.AccessControlParameter;
import accessstruct.Tool;
import accessstruct.lsss.LSSSLW10Engine;
import accessstruct.lsss.LSSSPolicyEngine;
import accessstruct.parser.ParserUtils;
import accessstruct.parser.PolicySyntaxException;
import it.unisa.dia.gas.jpbc.Element;
import it.unisa.dia.gas.jpbc.Pairing;
import it.unisa.dia.gas.plaf.jpbc.pairing.PairingFactory;
import util.ConfigFileName;

import java.lang.invoke.MutableCallSite;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import scheme.UserManageBT;
import util.TreeNode;
import util.TreeUtil;

/**
 * 方案整体结构
 * 主要流程：
 * 1.初始化：CA初始化，AA初始化
 * 2.密钥生成：，属性盲化，属性认证密钥，以及用户属性密钥
 * 3.离线加密
 * 4.在线加密
 * 5.属性认证
 * 6.外包解密
 * 7.本地解密
 * @Notion 类中存在不使用在线离线和外包解密的解密和解密方法，仅供测试使用
 */
public class LWBAMAABE {
    /**
     * 系统初始化阶段 包括CA初始化，AA初始化
     */
    public static long setUp(ConfigFileName config,int aaNums,String[] GlobalAttList,UserManageBT BT,int n) {

        long t1 = scheme.CA.setup(config,BT,n);
        System.out.println("+++CA初始化时间: "+t1+"  ms");
        System.out.println("Step 系统完成初始化=========================================================================");

        long t2= 0;
        int AttListLen = GlobalAttList.length;
        int averageLen = AttListLen/aaNums;
        for(int aid = 1; aid <= aaNums;aid++){
            String[] attOfAA = new String[averageLen];
            for(int j = 0;j<averageLen;j++){
                attOfAA[j] = GlobalAttList[(aid-1)*averageLen + j];
            }
            t2 = Math.max(scheme.AA.setup(config,aid,attOfAA),t2);
        }
        System.out.println("+++AA初始化最长时间: "+t2+"  ms");
        System.out.println("Step AA完成初始化==========================================================================");
        return t1 + t2;
    }

    /**
     * 密钥生成阶段
     */
    public static long keyGen(ConfigFileName config, String userID, int uid , String[] userAttList,int aaNums,UserManageBT BT) throws NoSuchAlgorithmException {

        long t = 0;
        int AttListLen = userAttList.length;
        int averageLen = AttListLen/aaNums;
        for(int aid = 1 ; aid <= aaNums;aid++){
            String[] userAttL = new String[averageLen];
            for(int j = 0;j<averageLen;j++){
                userAttL[j] = userAttList[(aid-1)*averageLen + j];
            }
            t= Math.max(scheme.AA.keygen(config, userID , userAttL,aid),t);
        }
        CA.AccountKeyGen( config, aaNums, BT, uid, userID);
        System.out.println("+++属性密钥生成最长时间: "+t+"  ms");
        System.out.println("Step 完成用户密钥生成=======================================================================");
        return  t;
    }

    /**
     * 用户代理密钥生成
     */
    public static long pxKeyGen(ConfigFileName config, String userID, int uid , String[] userAttList ,int aaNums,UserManageBT BT) throws NoSuchAlgorithmException {
        /*********************************************获取公共参数*******************************************************/
        //准备工作获取公参

        Pairing bp = PairingFactory.getPairing(config.getPairingParametersFileName());
        Properties UserSkProp = Tool.loadPropFromFile(config.getUserSKFileName());
        Properties TKProp = Tool.loadPropFromFile(config.getTKFileName());

        /*********************************************获取公共参数*******************************************************/
        Element theta = bp.getZr().newRandomElement().getImmutable();
        UserSkProp.setProperty("RK_"+userID, Base64.getEncoder().withoutPadding().encodeToString(theta.toBytes()));
        List<TreeNode> Path_uid = TreeUtil.getPath(BT.getBT().get(uid));

        long startTime = System.currentTimeMillis();
        for(TreeNode node_i : Path_uid)
        {
            String xi_tString = UserSkProp.getProperty("x_it-"+node_i.getId()+"-"+userID);
            Element x_it = bp.getZr().newElementFromBytes(Base64.getDecoder().decode(xi_tString)).getImmutable();////正确
            Element x_it_t = x_it.mul(theta).getImmutable();;
            TKProp.setProperty("x_itt-"+node_i.getId()+"-"+userID, Base64.getEncoder().withoutPadding().encodeToString(x_it_t.toBytes()));//
        }
        int avLen = userAttList.length/aaNums;
        for(int aid = 1 ; aid <= aaNums;aid++) {

            String KString = UserSkProp.getProperty("K-"+aid+"-"+userID);
            Element K = bp.getG1().newElementFromBytes(Base64.getDecoder().decode(KString)).getImmutable();////正确
            String K_uString = UserSkProp.getProperty("K_u-"+aid+"-"+userID);
            Element K_u = bp.getG1().newElementFromBytes(Base64.getDecoder().decode(K_uString)).getImmutable();////正确
            String L_0_String = UserSkProp.getProperty("L0-"+aid+"-"+userID);
            Element L_0 = bp.getG1().newElementFromBytes(Base64.getDecoder().decode(L_0_String)).getImmutable();//正确
            String L_1_String = UserSkProp.getProperty("L1-"+aid+"-"+userID);
            Element L_1 = bp.getG1().newElementFromBytes(Base64.getDecoder().decode(L_1_String)).getImmutable();//正确

            Element K_t = K.powZn(theta.invert()).getImmutable();;
            Element K_u_t = K_u.powZn(theta.invert()).getImmutable();;
            Element L_0_t = L_0.powZn(theta.invert()).getImmutable();;
            Element L_1_t = L_1.powZn(theta.invert()).getImmutable();;

            TKProp.setProperty("K_ut-"+aid+"-"+userID, Base64.getEncoder().withoutPadding().encodeToString(K_u_t.toBytes()));
            TKProp.setProperty("Kt-"+aid+"-"+userID, Base64.getEncoder().withoutPadding().encodeToString(K_t.toBytes()));
            TKProp.setProperty("L0t-"+aid+"-"+userID, Base64.getEncoder().withoutPadding().encodeToString(L_0_t.toBytes()));
            TKProp.setProperty("L1t-"+aid+"-"+userID, Base64.getEncoder().withoutPadding().encodeToString(L_1_t.toBytes()));
            String[] attOfAA = new String[avLen];
            for (int j = 0; j < avLen; j++) {
                //AA-aid 负责分发密钥所对应的属性
                attOfAA[j] = userAttList[(aid - 1) * avLen + j];
            }
            for(String att :attOfAA){
                String K_i_String = UserSkProp.getProperty("K"+att+"-"+aid+"-"+userID);
                Element K_i = bp.getG1().newElementFromBytes(Base64.getDecoder().decode(K_i_String)).getImmutable();//正确
                Element K_i_t =K_i.powZn(theta.invert()).getImmutable();;//正确
                TKProp.setProperty("K"+att+"t-"+aid+"-"+userID, Base64.getEncoder().withoutPadding().encodeToString(K_i_t.toBytes()));
            }
        }
        long endTime = System.currentTimeMillis();
        Tool.storePropToFile(UserSkProp,config.getUserSKFileName());
        Tool.storePropToFile(TKProp,config.getTKFileName());
        System.out.println("+++代理解密密钥生成时间: "+(endTime-startTime)+"  ms");
        System.out.println("Step 完成代理属性密钥生成====================================================================");
        return  endTime-startTime;
    }
    /**
     * 用户离线加密的操作
     */
    public static long offlineEncrypt(ConfigFileName config, String[] attInAcp, int aaNums, AccessControlParameter ACP, UserManageBT BT) throws NoSuchAlgorithmException, PolicySyntaxException {

        /*********************************************获取公共参数*******************************************************/
        //准备工作
        Pairing bp = PairingFactory.getPairing(config.getPairingParametersFileName());
        //获取全局参数GP
        Properties CAPKProp = Tool.loadPropFromFile(config.getGPFileName());
        Properties AAPKProp = Tool.loadPropFromFile(config.getAAPKFileName());
        Properties ctProp = Tool.loadPropFromFile(config.getCtFileName());

        String gString = CAPKProp.getProperty("g");
        Element g = bp.getG1().newElementFromBytes(Base64.getDecoder().decode(gString)).getImmutable();
        String wString = CAPKProp.getProperty("w");
        Element w = bp.getG1().newElementFromBytes(Base64.getDecoder().decode(wString)).getImmutable();
        String vString = CAPKProp.getProperty("v");
        Element v = bp.getG1().newElementFromBytes(Base64.getDecoder().decode(vString)).getImmutable();
        /**********************************************制定访问策略*****************************************************/
        //随选秘密值s
        Element s = bp.getZr().newRandomElement().getImmutable();//与密文有关
//        Element zero = bp.getZr().newZeroElement().getImmutable();//与属性认证有关
        //进行秘密共享

//        Map<String, Element> omegas =LSSSPolicyEngine.secretSharing(bp,zero,ACP);
        /************************************************计算密文*******************************************************/
        long EncryptStartTime = System.currentTimeMillis();
        //计算C_0;
        Element C0 = g.powZn(s).getImmutable();
        ctProp.setProperty("C0", Base64.getEncoder().withoutPadding().encodeToString(C0.toBytes()));

        List<TreeNode> MCS = BT.getMCS();
        for(TreeNode node_i : MCS)
        {
            Element T_j = node_i.getYi().powZn(s).getImmutable();
            ctProp.setProperty("T_j-"+node_i.getId(), Base64.getEncoder().withoutPadding().encodeToString(T_j.toBytes()));//
        }
        //开始计算C1 ：Ci1；Ci2: Ci3
        int avLen = attInAcp.length/aaNums;
        for(int aid = 1 ; aid <= aaNums;aid++){
            String[] attOfAA = new String[avLen];
            for(int j = 0;j<avLen;j++){
                //AA-aid 负责分发密钥所对应的属性
                attOfAA[j] = attInAcp[(aid-1)*avLen + j];
            }
            int [][] accessPolicy = ParserUtils.GenerateAccessPolicy(Tool.convertToAcP(attOfAA));
            String[] rho = ParserUtils.GenerateRhos(Tool.convertToAcP(attOfAA));
            AccessControlParameter AcP = LSSSLW10Engine.generateAccessControl(accessPolicy ,rho);
            Map<String, Element> lambdas =LSSSPolicyEngine.secretSharing(bp,s,AcP);

            //计算C_1
            String galphaString = AAPKProp.getProperty("g_alpha-"+aid);
            Element g_alpha = bp.getG1().newElementFromBytes(Base64.getDecoder().decode(galphaString)).getImmutable();
            Element C1 = g_alpha.powZn(s).getImmutable();
            ctProp.setProperty("C1-"+aid, Base64.getEncoder().withoutPadding().encodeToString(C1.toBytes()));
            for (String att:attOfAA) {

                //随机选取l个参数z_i \in Zp
                Element z_i = bp.getZr().newRandomElement().getImmutable();
                //属性值
                byte[] attHash = Tool.sha1(att);
                Element rhoI = bp.getZr().newElementFromHash(attHash, 0, attHash.length).getImmutable();

                Element C_i_1 = w.powZn(lambdas.get(att)).mul(v.invert().powZn(z_i)).getImmutable();
                Element C_i_2 = g.powZn(lambdas.get(att)).mul(g.invert().powZn(z_i).powZn(rhoI)).getImmutable();
                Element C_i_3 = g.powZn(z_i).getImmutable();

                ctProp.setProperty("C" + att + "1-"+aid, Base64.getEncoder().withoutPadding().encodeToString(C_i_1.toBytes()));
                ctProp.setProperty("C" + att + "2-"+aid, Base64.getEncoder().withoutPadding().encodeToString(C_i_2.toBytes()));
                ctProp.setProperty("C" + att + "3-"+aid, Base64.getEncoder().withoutPadding().encodeToString(C_i_3.toBytes()));
            }
        }
        long EncryptEndTime = System.currentTimeMillis();
        //存储密文
        Properties sProp = new Properties();
//        Properties sProp = loadPropFromFile(config.getSecretFileName());
        sProp.setProperty("s",Base64.getEncoder().withoutPadding().encodeToString(s.toBytes()));
        Tool.storePropToFile(sProp, config.getSecretFileName());
        Tool.storePropToFile(ctProp, config.getCtFileName());
        System.out.println("+++离线加密时间: "+(EncryptEndTime-EncryptStartTime)+"  ms");
        System.out.println("Step 离线加密阶段结束=======================================================================");
        return EncryptEndTime- EncryptStartTime;
    }

    /**
     * 用户在线加密
     * @param config
     * @param message
     * @param aaNums
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static long onlineEncrypt(ConfigFileName config, Element message,int aaNums) throws NoSuchAlgorithmException {
        /*********************************************获取公共参数*******************************************************/
        //准备工作
        Pairing bp = PairingFactory.getPairing(config.getPairingParametersFileName());
        Properties AAPKProp = Tool.loadPropFromFile(config.getAAPKFileName());
        Properties sProp  = Tool.loadPropFromFile(config.getSecretFileName());
        //随机选取秘密值
        String sString = sProp.getProperty("s");
        Element s = bp.getZr().newElementFromBytes(Base64.getDecoder().decode(sString)).getImmutable();
        Properties ctProp = Tool.loadPropFromFile(config.getCtFileName());

        Element egg_sum = bp.getGT().newOneElement().getImmutable();
        long onlineEncryptStartTime = System.currentTimeMillis();
        //加密密文
        for(int aid = 1;aid <= aaNums;aid++){
            String egg_betaString = AAPKProp.getProperty("egg_beta-"+aid);
            Element egg_beta = bp.getGT().newElementFromBytes(Base64.getDecoder().decode(egg_betaString)).getImmutable();
            egg_sum = egg_sum.mul(egg_beta.powZn(s));
        }
        Element C_v =Tool.GthashToZp(bp,egg_sum).getImmutable();;
        //最终要存储在远端的密文
        Element C = message.mul(egg_sum).getImmutable();
        long onlineEncryptEndTime = System.currentTimeMillis();
        ctProp.setProperty("C", Base64.getEncoder().withoutPadding().encodeToString(C.toBytes()));
        ctProp.setProperty("C_v", Base64.getEncoder().withoutPadding().encodeToString(C_v.toBytes()));
        Tool.storePropToFile(ctProp, config.getCtFileName());

        System.out.println("+++在线加密时间: "+(onlineEncryptEndTime - onlineEncryptStartTime)+"  ms");
        System.out.println("Step 在线加密阶段完成=======================================================================");
        return onlineEncryptEndTime - onlineEncryptStartTime;
    }
    /**
     * 用户本地解密
     */
    public static LWBAMAABE.Pair<Element, Long> localDecrypt(ConfigFileName config, String userID)
            throws NoSuchAlgorithmException {
        //准备工作
        Pairing bp = PairingFactory.getPairing(config.getPairingParametersFileName());
        //获取本地解密密钥
        Properties UserskProp = Tool.loadPropFromFile(config.getUserSKFileName());
        Properties CtProp = Tool.loadPropFromFile(config.getCtFileName());
        //密文原文
        String CString = CtProp.getProperty("C");
        Element C = bp.getGT().newElementFromBytes(Base64.getDecoder().decode(CString)).getImmutable();
        //半解密密文
        String midCtString = CtProp.getProperty("midCt");
        Element midCt = bp.getGT().newElementFromBytes(Base64.getDecoder().decode(midCtString)).getImmutable();

        //本地解密密钥
        String rk_thetaString = UserskProp.getProperty("RK_"+userID);
        Element rk_theta = bp.getZr().newElementFromBytes(Base64.getDecoder().decode(rk_thetaString)).getImmutable();
        long startTime = System.currentTimeMillis();
        Element m = C.div(midCt.powZn(rk_theta));
        //中间密文
        long endTime = System.currentTimeMillis();

        System.out.println("+++本地解密时间: "+(endTime - startTime)+"  ms");
        System.out.println("Step  本地解密结束=========================================================================");
        return new LWBAMAABE.Pair(m, endTime - startTime);
    }

    /**
     * 自定义pair模板
     * @param <K>
     * @param <V>
     */
    public static class Pair<K,V> {

        private K key;
        public K getKey() { return key; }
        private V value;
        public V getValue() { return value; }
        public Pair(K key,  V value) {
            this.key = key;
            this.value = value;
        }

    }

}

